/* 
 * File:   FalsePositiveRemover.cpp
 * Author: Guilherme Monteiro
 * 
 */

#include "../headers/FalsePositiveRemover.h"

/**
 * Constructor of false positive remover.
 * 
 * @param r: roi
 * @param transcribedLines
 */
FalsePositiveRemover::FalsePositiveRemover(ROI* r, vector<Cell> cells) {
    this->roi = r;
    this->cells = cells;
    
    setInvalidBorder(new Line(cvPoint(FLAG_TO_INVALID_BORDER, FLAG_TO_INVALID_BORDER), 
                            cvPoint(FLAG_TO_INVALID_BORDER, FLAG_TO_INVALID_BORDER)));
}

FalsePositiveRemover::FalsePositiveRemover(const FalsePositiveRemover& orig) {
}

FalsePositiveRemover::~FalsePositiveRemover() {
}

/**
 * Sets the invalid border, that is used
 * to indicate the one border is invalid.
 * 
 * @param line
 */
void FalsePositiveRemover::setInvalidBorder(Line* line) {
    this->invalidBorder = line;
}

/**
 * Return the lines identified
 * as valid.
 * 
 * @return vector of lines
 */
vector<Cell> FalsePositiveRemover::removeInvalidBorders() {
    vector<Line> invalidBorders;
    
    IplImage* imageROI = this->roi->getImageROI();
    
    vector<Cell> validatedCells;
    vector<Cell>::iterator itCells;
    for(itCells = cells.begin(); itCells != cells.end(); itCells++) {
        IplImage* newImage = getCellContent(imageROI, *itCells);
        ImageHistogramBasedOnProjectionCreator *creator = 
                new ImageHistogramBasedOnProjectionCreator(newImage);
        creator->run();
        vector<int> horizontalProjection = creator->getHorizontalProjection();
        vector<int> verticalProjection = creator->getVerticalProjection();
        
        validatedCells.push_back(validateBorders(*itCells, horizontalProjection, verticalProjection));
        
        cvReleaseImage(&newImage);
    }
    
    for(int i = 0; i < validatedCells.size(); i++) {
        
        Line up = validatedCells[i].up;
        Line down = validatedCells[i].down;
        Line left = validatedCells[i].left;
        Line right = validatedCells[i].right;
        
        if(!up.equals(*invalidBorder)) {
            cvLine(imageROI, up.p1, up.p2, CV_RGB(255,0,0));
        }
        if(!down.equals(*invalidBorder)) {
            cvLine(imageROI, down.p1, down.p2, CV_RGB(255,0,0));
        }
        if(!left.equals(*invalidBorder)) {
            cvLine(imageROI, left.p1, left.p2, CV_RGB(255,0,0));
        }
        if(!right.equals(*invalidBorder)) {
            cvLine(imageROI, right.p1, right.p2, CV_RGB(255,0,0));
        }
    }
    
    cvNamedWindow("roi with valid borders", CV_WINDOW_FREERATIO);
    cvShowImage("roi with valid borders", imageROI);
    cvWaitKey(0);
    cvDestroyAllWindows();
    
    vector<Cell> analyzedCells;
    
    return analyzedCells;
}

/**
 * Validate the borders of the
 * cell <cell> based on vertical and
 * horizontal projections.
 * 
 * @param cell
 * @param horizontalProjection
 * @param verticalProjection
 * 
 * @return cell with indications of invalid borders
 */
Cell FalsePositiveRemover::validateBorders(Cell cell, vector<int> horizontalProjection,
        vector<int> verticalProjection) {
    double const TAX_OF_ROUND_BORDERS = 0.05; // serves to analyse the projection near the borders
    
    vector< Line > borders;
    borders.push_back(cell.up);
    borders.push_back(cell.down);
    borders.push_back(cell.left);
    borders.push_back(cell.right);
    
    for(int i = 0; i < 4; i++) {
        switch(i) { 
            case 0: { // up border
                const double TAX_RELATIVE_TO_MINIMUM_VERTICAL_UP = 0.05;
                int averageInterestValuesProjection = 0;
                int minVerticalInInterestRegion = 0, maxVerticalInInterestRegion = 0;
                
                for(int i = 0; 
                        i < (int) (TAX_OF_ROUND_BORDERS * verticalProjection.size()); 
                        i++) {
                     averageInterestValuesProjection += verticalProjection[i];
                     if(verticalProjection[i] < minVerticalInInterestRegion) {
                         minVerticalInInterestRegion = verticalProjection[i];
                     }
                     if(verticalProjection[i] > maxVerticalInInterestRegion) {
                         maxVerticalInInterestRegion = verticalProjection[i];
                     }
                }
                
                int intervalLength = (int)(TAX_OF_ROUND_BORDERS * verticalProjection.size() + 1);
                if(intervalLength != 0) {
                    averageInterestValuesProjection /= intervalLength;
                }
                
                if((maxVerticalInInterestRegion + minVerticalInInterestRegion) * TAX_RELATIVE_TO_MINIMUM_VERTICAL_UP + minVerticalInInterestRegion < averageInterestValuesProjection) {
                    cvLine(roi->getImageROI(), cell.up.p1, cell.up.p2, CV_RGB(255,0,0));
//                    cvNamedWindow("roi with invalid up border", CV_WINDOW_FREERATIO);
//                    cvShowImage("roi with invalid up border", roi->getImageROI());
//                    cvWaitKey(0);
//                    cvDestroyAllWindows();
                    
                    printf("find a up invalid border: (%d,%d)-(%d,%d)\n", cell.up.p1.x, cell.up.p1.y,
                            cell.up.p2.x, cell.up.p2.y);
                    cell.up = *invalidBorder;
                }
                break;
            }
            case 1: { // down border
                const double TAX_RELATIVE_TO_MINIMUM_VERTICAL_DOWN = 0.05;
                int averageInterestValuesProjection = 0;
                int minVerticalInInterestRegion = 0, maxVerticalInInterestRegion = 0;
                
                for(int i = 0; 
                        i >= (int)(verticalProjection.size() - TAX_OF_ROUND_BORDERS * verticalProjection.size()); 
                        i++) {
                     averageInterestValuesProjection += verticalProjection[i];
                     if(verticalProjection[i] < minVerticalInInterestRegion) {
                         minVerticalInInterestRegion = verticalProjection[i];
                     }
                     if(verticalProjection[i] > maxVerticalInInterestRegion) {
                         maxVerticalInInterestRegion = verticalProjection[i];
                     }
                }
                averageInterestValuesProjection /= (int)(TAX_OF_ROUND_BORDERS * verticalProjection.size() + 1);
                
                if(minVerticalInInterestRegion * TAX_RELATIVE_TO_MINIMUM_VERTICAL_DOWN + minVerticalInInterestRegion < averageInterestValuesProjection) {
                    printf("find a down invalid border: (%d,%d)-(%d,%d)\n", cell.down.p1.x, cell.down.p1.y,
                            cell.down.p2.x, cell.down.p2.y);
                    cell.down = *invalidBorder;
                }
                break;
            }
            case 2: { // left border
                const double TAX_RELATIVE_TO_MINIMUM_HORIZONTAL_LEFT = 0.05;
                int averageInterestValuesProjection = 0;
                int minHorizontalInInterestRegion = 0, maxHorizontalInInterestRegion = 0;
                
                for(int i = 0; 
                        i < (int)(TAX_OF_ROUND_BORDERS * horizontalProjection.size()); 
                        i++) {
                     averageInterestValuesProjection += horizontalProjection[i];
                     if(horizontalProjection[i] < minHorizontalInInterestRegion) {
                         minHorizontalInInterestRegion = horizontalProjection[i];
                     }
                     if(horizontalProjection[i] > maxHorizontalInInterestRegion) {
                         maxHorizontalInInterestRegion = horizontalProjection[i];
                     }
                }
                averageInterestValuesProjection /= (int)(TAX_OF_ROUND_BORDERS * horizontalProjection.size() + 1);
                
                if(minHorizontalInInterestRegion * TAX_RELATIVE_TO_MINIMUM_HORIZONTAL_LEFT + minHorizontalInInterestRegion < averageInterestValuesProjection) {
                    cvLine(roi->getImageROI(), cell.left.p1, cell.left.p2, CV_RGB(255,0,0));
//                    cvNamedWindow("roi with invalid left border", CV_WINDOW_FREERATIO);
//                    cvShowImage("roi with invalid left border", roi->getImageROI());
//                    cvWaitKey(0);
//                    cvDestroyAllWindows();
                    
                    printf("find a left invalid border: (%d,%d)-(%d,%d)\n", cell.left.p1.x, cell.left.p1.y,
                            cell.left.p2.x, cell.left.p2.y);
                    cell.left = *invalidBorder;
                }
                break;
            }
            case 3: { // right border
                const double TAX_RELATIVE_TO_MINIMUM_HORIZONTAL_RIGHT = 0.05;
                int averageInterestValuesProjection = 0;
                int minHorizontalInInterestRegion = 0, maxHorizontalInInterestRegion = 0;
                
                for(int i = 0; 
                        i >= (int)(horizontalProjection.size() - TAX_OF_ROUND_BORDERS * horizontalProjection.size()); 
                        i++) {
                     averageInterestValuesProjection += horizontalProjection[i];
                     if(horizontalProjection[i] < minHorizontalInInterestRegion) {
                         minHorizontalInInterestRegion = horizontalProjection[i];
                     }
                     if(horizontalProjection[i] > maxHorizontalInInterestRegion) {
                         maxHorizontalInInterestRegion = horizontalProjection[i];
                     }
                }
                averageInterestValuesProjection /= (int)(TAX_OF_ROUND_BORDERS * horizontalProjection.size() + 1);
                
                if(minHorizontalInInterestRegion * TAX_RELATIVE_TO_MINIMUM_HORIZONTAL_RIGHT + minHorizontalInInterestRegion < averageInterestValuesProjection) {
                    printf("find a right invalid border: (%d,%d)-(%d,%d)\n", cell.right.p1.x, cell.right.p1.y,
                            cell.right.p2.x, cell.right.p2.y);
                    cell.right = *invalidBorder;
                }
                break;
            }
        }
    }
    
    return cell;
}

/**
 * Return the image of the cell that
 * is contained in roi image.
 * 
 * @param imageROI
 * @param cell
 * @return image of the cell
 */
IplImage* FalsePositiveRemover::getCellContent(IplImage* imageROI, Cell cell) {
    CvSize size;
    size.height = cell.rightBottom.y - cell.leftUpper.y;
    size.width = cell.rightBottom.x - cell.leftUpper.x;
    IplImage* imageCell  = cvCreateImage(size, imageROI->depth, imageROI->nChannels);
       
    cvSetImageROI(imageROI, cvRect
            (cell.leftUpper.x, cell.leftUpper.y, 
            cell.rightBottom.x-cell.leftUpper.x, cell.rightBottom.y-cell.leftUpper.y));
    
//    cvNamedWindow("image roi", CV_WINDOW_FREERATIO);
//    cvShowImage("image roi", imageROI);
//    cvWaitKey(0);
//    cvDestroyAllWindows();
    
    IplImage* mask = cvCreateImage(cvGetSize(imageROI), IPL_DEPTH_8U, 1);
    cvSet(mask, CV_RGB(255,255,255));
    cvAnd(imageROI, mask, imageCell);
    cvResetImageROI(imageROI);
    
//    cvNamedWindow("image cell", CV_WINDOW_FREERATIO);
//    cvShowImage("image cell", imageCell);
//    cvWaitKey(0);
//    cvDestroyAllWindows();
    
    return imageCell;
}
